package com.zdya.combatdocuments.ethereum;

import cn.hutool.core.io.FileUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.zdya.combatdocuments.component.CombatDocumentConfig;
import com.zdya.combatdocuments.util.AjaxResult;
import com.zdya.combatdocuments.util.SystemUtils;
import org.bitcoinj.crypto.*;
import org.bitcoinj.wallet.DeterministicSeed;
import org.web3j.crypto.CipherException;
import org.web3j.crypto.ECKeyPair;
import org.web3j.crypto.Wallet;
import org.web3j.crypto.WalletFile;
import org.web3j.protocol.ObjectMapperFactory;
import org.web3j.protocol.Web3j;
import org.web3j.protocol.admin.Admin;
import org.web3j.protocol.admin.methods.response.PersonalUnlockAccount;
import org.web3j.protocol.core.Request;
import org.web3j.protocol.http.HttpService;
import org.web3j.utils.Numeric;

import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @Author:HYZ
 * @Date:2023-6-1
 */
public class EthWallet {

    private static String dPath = "/ethereum/";

    static {
        if (SystemUtils.isWindows()) {
//            dPath = "C:" + File.separator + "ethereum" + File.separator;
            if (CombatDocumentConfig.getProfile() == null) {
                dPath = "C:" + File.separator + "ethereum" + File.separator;
            } else {
                dPath = CombatDocumentConfig.getProfile() + File.separator + "ethereum" + File.separator;
            }
        }

    }


    public static class EthHDWallet {
        String privateKey;
        String publicKey;
        List<String> mnemonic;
        String mnemonicPath;
        String Address;
        String keystore;
        String message = "";

        public EthHDWallet(String message) {
            this.message = message;
        }

        public EthHDWallet(String privateKey, String publicKey, List<String> mnemonic, String mnemonicPath, String address, String keystore) {
            this.privateKey = privateKey;
            this.publicKey = publicKey;
            this.mnemonic = mnemonic;
            this.mnemonicPath = mnemonicPath;
            this.Address = address;
            this.keystore = keystore;
        }
    }

    public static String ETHADDRESS = "ethAddress";
    public static String ETHPRIVATEKEY = "ethPrivateKey";
    public static String ETHADDRESSMAP = "ethAddressMap";
    public static String ETHPASSWORD = "password";
    public static String ETHKeystore = "keystore";
    private static Web3j web3j;

    /**
     * 通用的以太坊基于bip44协议的助记词路径 （imtoken jaxx Metamask myetherwallet）
     */
    private static String ETH_TYPE = "m/44'/60'/0'/0/0";
    /**
     * 随机数
     */
    private static SecureRandom secureRandom = new SecureRandom();

    public static void main(String[] args) throws Exception {
//        Map map = new HashMap();
//        map.put("password", "12345678");
//        AjaxResult wallet1 = createWallet();
//        System.out.println(JSONUtil.toJsonStr(wallet1));
//        Map data = (Map) wallet1.getData();
//        String address = String.valueOf(data.get("address"));
//        String keystore = String.valueOf(data.get("keystore"));
//        AjaxResult ajaxResult = uploadAndUnlock("123", address, keystore);

//        String address = "0x90f629b9b50602180f8d92b773b6fe8c630752d5";
//        String keystore = "{\"address\":\"90f629b9b50602180f8d92b773b6fe8c630752d5\",\"id\":\"4487d12e-36b4-4eda-bc3c-34a538139031\",\"version\":3,\"crypto\":{\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"680c919775107a016d5f43c671edf2f1983b558bcaa7d3acb17f82bc1ba5aaff\",\"cipherparams\":{\"iv\":\"a284a5d9880e519ff08e8c69a4f65c70\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":4096,\"p\":6,\"r\":8,\"salt\":\"7544f9eb335751fed99df07c2d2e71860d6d32178fa702716bbd3a96ef81db6b\"},\"mac\":\"a71850cb7c145f73af9b29b81f38dc8a64d59ec5116ce7c5fc8a7bb92e719cca\"}}";
//        //解锁并上传钱包
//        AjaxResult ajaxResult = uploadAndUnlock("123", address, keystore);
//        System.out.println(JSONUtil.toJsonStr(ajaxResult));
//        System.out.println(s);

//        AjaxResult wallet = EthWallet.createWallet();
//        HashMap<String, Object> data = (HashMap<String, Object>) wallet.getData();
//        String add = (String) data.get("address");
//        String key = (String) data.get("keystore");
//        System.out.println(add+"----->"+key);
       String address = "0xbd9f16c701f3fd2f7eab7e9c8575d2d03d01498d";

        unlockWallet("127.0.0.1",address,"12345678");


    }


    /**
     * 创建钱包
     *
     * @return 私钥、公钥、助记词、分层钱包路径、keystore、adderss
     */

    public static AjaxResult createWallet() throws Exception {

        AjaxResult ru = new AjaxResult();
        //创建钱包不需要-节点地址
        //密码格式判断
        EthHDWallet ethHDWallet = generateMnemonic(ETH_TYPE);
        if (ethHDWallet != null) {
            //返回创建的钱包信息
            ru.setCode("0");
            ru.setData(new HashMap<String, Object>() {{
                //put( "publicKey", ethHDWallet.publicKey );
                //put( "privateKey", ethHDWallet.privateKey );
                put("address", ethHDWallet.Address);
                put("keystore", ethHDWallet.keystore);
                //put( "mnemonic", ethHDWallet.mnemonic );
                //put( "mnemonicPath", ethHDWallet.mnemonicPath );
            }});
            ru.setMessage("钱包新建成功！");
        } else {
            ru.setCode("-1");
            ru.setMessage("创建钱包失败-创建过程出错");
        }
        return ru;
    }


    /**
     * 生成一个随机数种子；
     * 通过随机数种子得到助记词；
     * 通过 种子 + 路径 派生生成私钥；
     * 使用KeyStore保存私钥；
     * 私钥推倒出账号地址
     */
    private static EthHDWallet generateMnemonic(String path) {
        if (!path.startsWith("m") && !path.startsWith("M")) {
            //参数非法
            return null;
        }
        String[] pathArray = path.split("/");
        if (pathArray.length <= 1) {
            //内容不对
            return null;
        }

        String passphrase = "";
        //当前时间
        //long creationTimeSeconds = System.currentTimeMillis() / 1000;
        //随机数种子
        DeterministicSeed ds = new DeterministicSeed(secureRandom, 128, passphrase);
        //创建钱包
        EthHDWallet ethHDWallet = createWallet(ds, pathArray);

        return ethHDWallet;
    }

    /**
     * 根据种子创建钱包
     */
    private static EthHDWallet createWallet(DeterministicSeed ds, String[] pathArray) {
        String password = "12345678";
        //根私钥
        byte[] seedBytes = ds.getSeedBytes();
        System.out.println("根私钥 " + Arrays.toString(seedBytes));
        //助记词
        List<String> mnemonic = ds.getMnemonicCode();
        System.out.println("助记词 " + Arrays.toString(mnemonic.toArray()));
        try {
            //助记词种子
            byte[] mnemonicSeedBytes = MnemonicCode.INSTANCE.toEntropy(mnemonic);
            System.out.println("助记词种子 " + Arrays.toString(mnemonicSeedBytes));
            ECKeyPair mnemonicKeyPair = ECKeyPair.create(mnemonicSeedBytes);
            WalletFile walletFile = Wallet.createLight(password, mnemonicKeyPair);
            //生成keystore
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            String jsonStr = objectMapper.writeValueAsString(walletFile);
            System.out.println("mnemonic keystore " + jsonStr);
            //验证操作
            WalletFile checkWalletFile = objectMapper.readValue(jsonStr, WalletFile.class);
            ECKeyPair ecKeyPair = Wallet.decrypt(password, checkWalletFile);
            byte[] checkMnemonicSeedBytes = Numeric.hexStringToByteArray(ecKeyPair.getPrivateKey().toString(16));
            System.out.println("验证助记词种子 "
                    + Arrays.toString(checkMnemonicSeedBytes));
            List<String> checkMnemonic = MnemonicCode.INSTANCE.toMnemonic(checkMnemonicSeedBytes);
            System.out.println("验证助记词 " + Arrays.toString(checkMnemonic.toArray()));

        } catch (MnemonicException.MnemonicLengthException | MnemonicException.MnemonicWordException | MnemonicException.MnemonicChecksumException | CipherException | IOException e) {
            e.printStackTrace();
        }

        if (seedBytes == null) {
            return null;
        }

        DeterministicKey dkKey = HDKeyDerivation.createMasterPrivateKey(seedBytes);
        for (int i = 1; i < pathArray.length; i++) {
            ChildNumber childNumber;
            if (pathArray[i].endsWith("'")) {
                int number = Integer.parseInt(pathArray[i].substring(0,
                        pathArray[i].length() - 1));
                childNumber = new ChildNumber(number, true);
            } else {
                int number = Integer.parseInt(pathArray[i]);
                childNumber = new ChildNumber(number, false);
            }
            dkKey = HDKeyDerivation.deriveChildKey(dkKey, childNumber);
        }

        System.out.println("path " + dkKey.getPathAsString());
        ECKeyPair keyPair = ECKeyPair.create(dkKey.getPrivKeyBytes());
        System.out.println("eth privateKey " + keyPair.getPrivateKey().toString(16));
        System.out.println("eth publicKey " + keyPair.getPublicKey().toString(16));


        EthHDWallet ethHDWallet = null;
        try {
            WalletFile walletFile = Wallet.createLight(password, keyPair);
            System.out.println("eth address " + "0x" + walletFile.getAddress());
            ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
            //存
            String jsonStr = objectMapper.writeValueAsString(walletFile);
            System.out.println("eth keystore " + jsonStr);

            ethHDWallet = new EthHDWallet(keyPair.getPrivateKey().toString(16),
                    keyPair.getPublicKey().toString(16),
                    mnemonic, dkKey.getPathAsString(),
                    "0x" + walletFile.getAddress(), jsonStr);
        } catch (CipherException | JsonProcessingException e) {
            e.printStackTrace();
        }

        return ethHDWallet;

    }


    /**
     * 上传解锁钱包
     *
     * @param chainName 链名
     * @param address   钱包地址
     * @param keyStore
     * @return
     * @throws Exception
     */
    public static AjaxResult uploadAndUnlock(String chainName, String address, String keyStore) throws Exception {

        AjaxResult ru = new AjaxResult();
        if (StringUtils.isNotBlank(chainName) && StringUtils.isNotBlank("address") && StringUtils.isNotBlank("keyStore")) {
            String password = "12345678";
            String peerIp = "127.0.0.1";
            if (uploadKeystore(chainName, keyStore, address)) {
                String result = unlockWallet(peerIp, address, password);
                if (result != null) {
                    Map hashMap = JSONUtil.parseObj(result);
                    if (hashMap.get("error") == null) {
                        //上传并且解锁成功
                        ru.setCode("1");
                        ru.setMessage("上传解锁成功");
                    } else {
                        //具体错误原因
                        Map errorMap = JSONUtil.parseObj(hashMap.get("error"));
                        ru.setCode("0");
                        ru.setMessage(errorMap.get("message").toString());
                    }
                } else {
                    ru.setCode("0");
                    ru.setMessage("上传解锁过程出错");
                }
            }
            //ru.setDatamap(contractCompile);
        } else {
            ru.setCode("0");
            ru.setMessage("钱包上传参数有误");
        }


        return ru;
    }


    /**
     * 上传Keystore 文件
     */
    private static boolean uploadKeystore(String chainName, String keystore, String address) {

//        String path = dPath + chainName + File.separator + "data" + File.separator + "keystore" + File.separator + address + ".keystore";
        String path = dPath + chainName + File.separator  + "keystore" + File.separator + address + ".keystore";
        FileUtil.writeBytes(keystore.getBytes(), path);


        return true;
    }

    /*
    解锁上传的钱包
     */
    public static String unlockWallet(String peerIp, String address, String password) throws IOException {

        String url = "http://" + peerIp + ":8545";
//        Web3j web3j = Web3j.build(new HttpService(url));

        Admin admin = Admin.build(new HttpService(url));
        Request<?, PersonalUnlockAccount> personalUnlockAccountRequest = admin.personalUnlockAccount(address, password);
        Boolean aBoolean = personalUnlockAccountRequest.send().accountUnlocked();

        if(aBoolean){
            return "success";
        }else {
            return null;
        }


       /* try {
            Thread.sleep(2000); //1000 毫秒，也就bai是1秒.
        } catch (InterruptedException ex) {
//        Thread.currentThread().interrupt();
        }
        peerIp = "http://" + peerIp + ":8545";
        String result = doPostOrGet(peerIp, "{\"jsonrpc\":\"2.0\",\"method\":\"web3_personal_unlockAccount\",\"params\":[" + "\"" + address + "\",\"" + password + "\"],\"id\":67}");
        return result;*/
    }


    /**
     * keystore解锁
     */
    public static EthHDWallet decryptWalletByKeystore(String keystore) {
        String password = "12345678";
        ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
        try {
            WalletFile walletFile = objectMapper.readValue(keystore, WalletFile.class);
            ECKeyPair ecKeyPair = Wallet.decrypt(password, walletFile);

            //通过私钥继续创建钱包
            return new EthHDWallet(ecKeyPair.getPrivateKey().toString(16),
                    ecKeyPair.getPublicKey().toString(16),
                    null, null,
                    "0x" + walletFile.getAddress(), keystore);
        } catch (CipherException e) {
            if ("Invalid password provided".equals(e.getMessage())) {
                System.out.println("密码错误");
                return new EthHDWallet("密码错误");
            } else {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * java http
     */
    private static String doPostOrGet(String pathUrl, String data) {
        OutputStreamWriter out = null;
        BufferedReader br = null;
        String result = "";
        try {
            URL url = new URL(pathUrl);
            //打开和url之间的连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            //请求方式
            conn.setRequestMethod("POST");
            //conn.setRequestMethod("GET");

            //设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)");
            conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");

            //DoOutput设置是否向httpUrlConnection输出，DoInput设置是否从httpUrlConnection读入，此外发送post请求必须设置这两个
            conn.setDoOutput(true);
            conn.setDoInput(true);
            System.out.println(data);
            /**
             * 下面的三句代码，就是调用第三方http接口
             */
            //获取URLConnection对象对应的输出流
            out = new OutputStreamWriter(conn.getOutputStream(), "UTF-8");
            //发送请求参数即数据
            out.write(data);
            //flush输出流的缓冲
            out.flush();

            /**
             * 下面的代码相当于，获取调用第三方http接口后返回的结果
             */
            //获取URLConnection对象对应的输入流
            InputStream is = conn.getInputStream();
            //构造一个字符流缓存
            br = new BufferedReader(new InputStreamReader(is));
            String str = "";
            while ((str = br.readLine()) != null) {
                result += str;
            }
            System.out.println(result);
            //关闭流
            is.close();
            //断开连接，disconnect是在底层tcp socket链接空闲时才切断，如果正在被其他线程使用就不切断。
            conn.disconnect();

            return result;

        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

}
